package org.ccf.certified.config.shiro.filter;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.ccf.certified.business.shiro.manager.TokenVerifyManager;
import org.ccf.certified.business.shiro.manager.UserManager;
import org.ccf.certified.business.shiro.model.TokenVerify;
import org.ccf.certified.business.shiro.model.User;
import org.ccf.certified.util.JWTUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * @author LiuFang
 * @date 2022/1/12
 */
@Service
public class AuthRealm extends AuthorizingRealm {

    private static final Logger log = LoggerFactory.getLogger(AuthRealm.class);

    @Value("${token.timeout}")
    private Integer TOKEN_TIMEOUT;

    @Autowired
    private UserManager userService;

    @Autowired
    private TokenVerifyManager tokenVerifyManager;

    /**
     * 大坑！，必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 只有当需要检测用户权限的时候才会调用此方法，例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = JWTUtil.getUsername(principals.toString());
//        UserBean user = userService.getUser(username);
        User user = userService.getUser(username);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//        simpleAuthorizationInfo.addRole(user.getRole());
//        Set<String> permission = new HashSet<>(Arrays.asList(user.getPermission().split(",")));
        simpleAuthorizationInfo.addRoles(user.getRoles());
        simpleAuthorizationInfo.addStringPermissions(user.getPermission());
        return simpleAuthorizationInfo;
    }

    /**
     * 默认使用此方法进行用户名正确与否验证，错误抛出异常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        // 解密获得username，用于和数据库进行对比
        String username = JWTUtil.getUsername(token);
        if (StringUtils.isBlank(username)) {
            throw new AuthenticationException("token invalid");
        }
//        UserBean userBean = userService.getUser(username);
        User userBean = userService.getUser(username);
        if (userBean == null) {
            throw new AuthenticationException("User didn't existed!");
        }

        /** 验证token真伪 and 是否超时 */
        try {
            QueryWrapper<TokenVerify> wrapper = new QueryWrapper();
            wrapper.eq("token", token);
            TokenVerify one = tokenVerifyManager.getOne(wrapper);
            /**token是否存在 || 验证token的时效*/
            if(one == null || new Date().getTime() > one.getExpiredTime().getTime()){
                throw new AuthenticationException("Token Timeout ... || Username or password error");
            }
            boolean verify = JWTUtil.verify(token, username, userBean.getPassword());
            if (!verify) {
                throw new AuthenticationException("Token Timeout ... || Username or password error");
            }else {
                /**刷新token时长*/
                Date date = new Date();
                date.setTime(date.getTime() + TOKEN_TIMEOUT);
                one.setExpiredTime(date);
                one.setUpdateCount(one.getUpdateCount() + 1);
                one.setUpdateTime(new Date());
                boolean update = tokenVerifyManager.update(one, wrapper);
                if(update){
                    log.info("update token success ...");
                }else {
                    log.error("update token fail ...");
                }
            }
        }catch (RuntimeException e){
            throw new AuthenticationException("Token Timeout ... || Username or password error");
        }

        return new SimpleAuthenticationInfo(token, token, "my_realm");
    }
}
